/* Copyright (c) Microsoft Corporation.
   Licensed under the MIT License. */

/***************************************************************************
	Author: ShonK
	Project: Kauai
	Reviewed:
	Copyright (c) Microsoft Corporation

	Masked bitmap routines that a tool might use.

***************************************************************************/
#include "frame.h"
ASSERTNAME


RTCLASS(MBMP)


/***************************************************************************
	Destructor for a masked bitmap.
***************************************************************************/
MBMP::~MBMP(void)
{
	AssertBaseThis(0);
	FreePhq(&_hqrgb);
}


/***************************************************************************
	Static method to create a new MBMP based on the given prgbPixels with
	extracting rectangle *prc and transparent color bTransparent.  prgbPixels
	should be a two-dimensional matrix of 8-bit pixels with width cbRow and
	height dyp.  prc should be the desired extracting rectangle.  xp and yp
	are the coordinates for the reference point of the MBMP (with (0,0) being
	the upper-left corner).  bTransparent should be the pixel value for the
	transparent color for the MBMP.
***************************************************************************/
PMBMP MBMP::PmbmpNew(byte *prgbPixels, long cbRow, long dyp, RC *prc,
	long xpRef, long ypRef, byte bTransparent, ulong grfmbmp, byte bDefault)
{
	AssertIn(cbRow, 1, kcbMax);
	AssertIn(dyp, 1, kcbMax);
	AssertPvCb(prgbPixels, LwMul(cbRow, dyp));
	AssertVarMem(prc);
	PMBMP pmbmp;

	if (pvNil != (pmbmp = NewObj MBMP) && !pmbmp->_FInit(prgbPixels, cbRow,
			dyp, prc, xpRef, ypRef, bTransparent, grfmbmp, bDefault))
		{
		ReleasePpo(&pmbmp);
		}
	return pmbmp;
}


/***************************************************************************
	Initialize the MBMP based on the given pixels.
***************************************************************************/
bool MBMP::_FInit(byte *prgbPixels, long cbRow, long dyp, RC *prc,
	long xpRef, long ypRef, byte bTransparent, ulong grfmbmp, byte bDefault)
{
	AssertIn(cbRow, 1, kcbMax);
	AssertIn(dyp, 1, kcbMax);
	AssertPvCb(prgbPixels, LwMul(cbRow, dyp));
	AssertVarMem(prc);
	Assert(!prc->FEmpty() && prc->xpLeft >= 0 && prc->xpRight <= cbRow &&
		prc->ypTop >= 0 && prc->ypBottom <= dyp, "Invalid rectangle");
	short *qrgcb;
	byte *pb, *pbRow, *pbLimRow;
	byte *qbDst, *qbDstPrev;
	long cbPixelData, cbPrev, cbOpaque, cbRun;
	long dibRow;
	bool fTrans, fMask;
	long xpMin, xpLim, ypLim, xp, yp;
	RC rc = *prc;

	// allocate enough space for the rgcb
	if (!FAllocHq(&_hqrgb, size(MBMPH) + LwMul(rc.Dyp(), size(short)),
			fmemNil, mprNormal))
		{
		return fFalse;
		}

	_Qmbmph()->fMask = fMask = FPure(grfmbmp & fmbmpMask);
	_Qmbmph()->bFill = bDefault;
	dibRow = (grfmbmp & fmbmpUpsideDown) ? -cbRow : cbRow;

	// crop the bitmap and get an upper bound on the size of the pixel data
	pbRow = prgbPixels + LwMul(cbRow,
		((grfmbmp & fmbmpUpsideDown) ? dyp - rc.ypTop - 1 : rc.ypTop));
	qrgcb = _Qrgcb();
	xpMin = rc.xpRight;
	xpLim = rc.xpLeft;
	ypLim = rc.ypTop;
	cbPixelData = 0;
	for (yp = rc.ypTop; yp < rc.ypBottom; pbRow += dibRow, yp++)
		{
		pb = pbRow + rc.xpLeft;
		pbLimRow = pbRow + rc.xpRight;
		cbPrev = cbPixelData;
		cbRun = cbOpaque = 0;
		fTrans = fTrue;
		for (;;)
			{
			// check for overflow or change in transparent status, or the
			// end of the row
			if (pb == pbLimRow || fTrans != (*pb == bTransparent) ||
					cbRun == kbMax)
				{
				if (fTrans && pb == pbLimRow)
					break;
				cbPixelData++;
				if (!fTrans && cbRun > 0)
					{
					xp = pb - pbRow;
					if (xpMin > xp - cbRun)
						xpMin = xp - cbRun;
					if (xpLim < xp)
						xpLim = xp;
					cbOpaque += cbRun;
					if (!fMask)
						cbPixelData += cbRun;
					}
				if (pb == pbLimRow)
					break;
				cbRun = 0;
				fTrans = !fTrans;
				}
			else
				{
				// Increment pixel count and go on to next pixel.
				cbRun++;
				pb++;
				}
			}

		if (0 == cbOpaque)
			{
			// nothing in this row but transparent pixels
			if (yp == rc.ypTop)
				rc.ypTop++;
			else
				qrgcb[yp - rc.ypTop] = 0;
			cbPixelData = cbPrev;
			}
		else
			{
			// set the row length in the rgcb.
			AssertIn(cbPixelData - cbPrev, 2 + !fMask, kswMax + 1);
			qrgcb[yp - rc.ypTop] = (short)(cbPixelData - cbPrev);
			ypLim = yp + 1;
			}
		}
	rc.ypBottom = ypLim;
	rc.xpLeft = xpMin;
	rc.xpRight = xpLim;
	if (rc.FEmpty())
		{
		Warn("Empty source bitmap for MBMP");
		rc.Zero();
		}

	//reallocate the _hqrgb to the size actually needed
	AssertIn(LwMul(rc.Dyp(), size(short)), 0,
		CbOfHq(_hqrgb) - size(MBMPH) + 1);

	_cbRgcb = LwMul(rc.Dyp(), size(short));
	if (!FResizePhq(&_hqrgb, _cbRgcb + size(MBMPH) + cbPixelData,
			fmemNil, mprNormal))
		{
		return fFalse;
		}

	//now actually construct the pixel data
	qrgcb = _Qrgcb();
	qbDst = (byte *)PvAddBv(qrgcb, _cbRgcb);
	pbRow = prgbPixels + LwMul(cbRow,
		((grfmbmp & fmbmpUpsideDown) ? dyp - rc.ypTop - 1 : rc.ypTop));
	for (yp = rc.ypTop; yp < rc.ypBottom; pbRow += dibRow, yp++)
		{
		if (qrgcb[yp - rc.ypTop] == 0)
			{
			//empty row, no need to scan it
			AssertIn(yp, rc.ypTop + 1, rc.ypBottom);
			continue;
			}

		pb = pbRow + rc.xpLeft;
		pbLimRow = pbRow + rc.xpRight;
		qbDstPrev = qbDst;
		cbRun = 0;
		fTrans = fTrue;
		for (;;)
			{
			// check for overflow or change in transparent status, or the
			// end of the row
			if (pb == pbLimRow || fTrans != (*pb == bTransparent) ||
					cbRun == kbMax)
				{
				if (fTrans && pb == pbLimRow)
					break;
				*qbDst++ = (byte)cbRun;
				if (!fTrans)
					{
					if (!fMask)
						{
						CopyPb(pb - cbRun, qbDst, cbRun);
						qbDst += cbRun;
						}
					if (pb == pbLimRow)
						break;
					}
				cbRun = 0;
				fTrans = !fTrans;
				}
			else
				{
				// Increment pixel count and go on to next pixel.
				cbRun++;
				pb++;
				}
			}

		// Set the row length in the rgcb.
		cbRun = qbDst - qbDstPrev;
		AssertIn(cbRun, 2 + !fMask, qrgcb[yp - rc.ypTop] + 1);
		qrgcb[yp - rc.ypTop] = (short)cbRun;
		}

	//shrink _hqrgb to the actual size needed
	cbRun = BvSubPvs(qbDst, QvFromHq(_hqrgb));
	AssertIn(cbRun, 0, CbOfHq(_hqrgb) + 1);
	AssertDo(FResizePhq(&_hqrgb, cbRun, fmemNil, mprNormal),
		"shrinking failed!");

	//set the bounding rectangle of the MBMP
	rc.Offset(-xpRef, -ypRef);
	_Qmbmph()->rc = rc;

	AssertThis(0);
	return fTrue;
}


/****************************************************************************
	This function will read in a bitmap file and return a PMBMP made from it.
	(xp, yp) will be the reference point of the mbmp [(0,0) is uppper-left].
	All pixels with the same value as bTransparent will be read in as
	transparent. The bitmap file must be uncompressed and have a bit depth
	of 8.  The palette information is be ignored.
****************************************************************************/
PMBMP MBMP::PmbmpReadNative(FNI *pfni, byte bTransparent, long xp, long yp,
	ulong grfmbmp, byte bDefault)
{
	AssertPo(pfni, ffniFile);
	byte *prgb;
	RC rc;
	long dxp, dyp;
	bool fUpsideDown;
	PMBMP pmbmp = pvNil;

	if (!FReadBitmap(pfni, &prgb, pvNil, &dxp, &dyp, &fUpsideDown, bTransparent))
		return pvNil;

	rc.Set(0, 0, dxp, dyp);
	pmbmp = MBMP::PmbmpNew(prgb, CbRoundToLong(rc.xpRight),
		rc.ypBottom, &rc, xp, yp, bTransparent,
		(fUpsideDown ? grfmbmp : grfmbmp ^ fmbmpUpsideDown), bDefault);
	FreePpv((void **)&prgb);

	return pmbmp;
}


/***************************************************************************
	Read a masked bitmap from a block.  May free the block or modify it.
***************************************************************************/
PMBMP MBMP::PmbmpRead(PBLCK pblck)
{
	AssertPo(pblck, 0);
	PMBMP pmbmp;
	MBMPH *qmbmph;
	long cbRgcb;
	long cbTot;
	bool fSwap;
	RC rc;
	HQ hqrgb = hqNil;

	if (!pblck->FUnpackData())
		return pvNil;
	cbTot = pblck->Cb();

	if (cbTot < size(MBMPH) || hqNil == (hqrgb = pblck->HqFree()))
		return pvNil;

	qmbmph = (MBMPH *)QvFromHq(hqrgb);
	fSwap = (kboOther == qmbmph->bo);
	if (fSwap)
		SwapBytesBom(qmbmph, kbomMbmph);
	else if (qmbmph->bo != kboCur)
		goto LFail;

	if (qmbmph->swReserved != 0 || qmbmph->cb != cbTot)
		goto LFail;

	rc = qmbmph->rc;
	if (rc.FEmpty())
		{
		if (cbTot != size(MBMPH))
			goto LFail;
		qmbmph->rc.xpRight = rc.xpLeft;
		qmbmph->rc.ypBottom = rc.ypTop;
		}

	cbRgcb = LwMul(rc.Dyp(), size(short));
	if (size(MBMPH) + cbRgcb > cbTot)
		goto LFail;

	if (pvNil == (pmbmp = NewObj MBMP))
		{
LFail:
		FreePhq(&hqrgb);
		return pvNil;
		}

	pmbmp->_cbRgcb = cbRgcb;
	pmbmp->_hqrgb = hqrgb;

	if (fSwap)
		{
		//swap bytes in the rgcb
		SwapBytesRgsw(pmbmp->_Qrgcb(), rc.Dyp());
		}

#ifdef DEBUG
	//verify the rgcb and rgb
	long ccb, cb, dxp;
	byte *qb;
	bool fMask = pmbmp->_Qmbmph()->fMask;
	short *qcb = pmbmp->_Qrgcb();
	byte *qbRow = (byte *)PvAddBv(qcb, cbRgcb);

	cbTot -= size(MBMPH) + cbRgcb;
	for (ccb = rc.Dyp(); ccb-- > 0; )
		{
		cb = *qcb++;
		if (!FIn(cb, 0, cbTot + 1))
			goto LFailDebug;
		cbTot -= cb;
		qb = qbRow;
		dxp = 0;
		while (qb < qbRow + cb)
			{
			dxp += *qb++;
			if (qb >= qbRow + cb)
				break;
			dxp += *qb;
			if (fMask)
				qb++;
			else
				qb += *qb + 1;
			}
		if (dxp > rc.Dxp() || qb != qbRow + cb)
			goto LFailDebug;
		qbRow = qb;
		}
	if (cbTot != 0)
		{
LFailDebug:
		Bug("Attempted to read bad MBMP");
		ReleasePpo(&pmbmp);
		}
#endif //DEBUG

	AssertNilOrPo(pmbmp, 0);
	return pmbmp;
}


/***************************************************************************
	Return the total size on file.
***************************************************************************/
long MBMP::CbOnFile(void)
{
	AssertThis(0);
	return CbOfHq(_hqrgb);
}


/***************************************************************************
	Write the masked bitmap (and its header) to the given block.
***************************************************************************/
bool MBMP::FWrite(PBLCK pblck)
{
	AssertThis(0);
	AssertPo(pblck, 0);
	MBMPH *qmbmph;

	qmbmph = _Qmbmph();
	qmbmph->bo = kboCur;
	qmbmph->osk = koskCur;
	qmbmph->swReserved = 0;
	qmbmph->cb = CbOfHq(_hqrgb);

	if (qmbmph->cb != pblck->Cb())
		{
		Bug("Wrong sized block");
		return fFalse;
		}

	if (!pblck->FWriteHq(_hqrgb, 0))
		return fFalse;

	return fTrue;
}


/***************************************************************************
	Get the natural rectangle for the mbmp.
***************************************************************************/
void MBMP::GetRc(RC *prc)
{
	AssertThis(0);
	*prc = _Qmbmph()->rc;
}


/***************************************************************************
	Return whether the given (xp, yp) is in a non-transparent pixel of
	the MBMP.  (xp, yp) should be given in MBMP coordinates.
***************************************************************************/
bool MBMP::FPtIn(long xp, long yp)
{
	AssertThis(0);
	byte *qb, *qbLim;
	short *qcb;
	short cb;
	MBMPH *qmbmph;

	qmbmph = _Qmbmph();
	if (!qmbmph->rc.FPtIn(xp, yp))
		return fFalse;

	qcb = _Qrgcb();
	qb = (byte *)PvAddBv(qcb, _cbRgcb);
	for (yp -= qmbmph->rc.ypTop; yp-- > 0; )
		qb += *qcb++;

	qbLim = qb + *qcb;
	for (xp -= qmbmph->rc.xpLeft; qb < qbLim; )
		{
		if (0 > (xp -= *qb++) || qb >= qbLim)
			break;
		cb = *qb++;
		if (0 > (xp -= cb))
			return fTrue;
		if (!qmbmph->fMask)
			qb += cb;
		}
	Assert(qb <= qbLim, "bad row in MBMP");
	return fFalse;
}


#ifdef DEBUG
/***************************************************************************
	Assert the validity of a MBMP.
***************************************************************************/
void MBMP::AssertValid(ulong grf)
{
	long ccb;
	long cbTot;
	short *qcb;
	RC rc;

	MBMP_PAR::AssertValid(0);
	AssertHq(_hqrgb);

	rc = _Qmbmph()->rc;
	ccb = rc.Dyp();
	Assert(_cbRgcb == LwMul(rc.Dyp(), size(short)),
		"_cbRgcb wrong");
	cbTot = 0;
	qcb = _Qrgcb();
	while (ccb-- > 0)
		{
		AssertIn(*qcb, 0, kcbMax);
		cbTot += *qcb++;
		}
	Assert(cbTot + _cbRgcb + size(MBMPH) == CbOfHq(_hqrgb),
		"_hqrgb wrong size");
}


/***************************************************************************
	Mark memory for the MBMP.
***************************************************************************/
void MBMP::MarkMem(void)
{
	AssertValid(0);
	MBMP_PAR::MarkMem();
	MarkHq(_hqrgb);
}
#endif //DEBUG


/***************************************************************************
	A PFNRPO to read an MBMP.
***************************************************************************/
bool MBMP::FReadMbmp(PCRF pcrf, CTG ctg, CNO cno, PBLCK pblck,
	PBACO *ppbaco, long *pcb)
{
	AssertPo(pcrf, 0);
	AssertPo(pblck, fblckReadable);
	AssertNilOrVarMem(ppbaco);
	AssertVarMem(pcb);
	PMBMP pmbmp;

	*pcb = pblck->Cb(fTrue);
	if (pvNil == ppbaco)
		return fTrue;

	if (!pblck->FUnpackData())
		goto LFail;
	*pcb = pblck->Cb();

	if (pvNil == (pmbmp = PmbmpRead(pblck)))
		{
LFail:
		TrashVar(ppbaco);
		TrashVar(pcb);
		return fFalse;
		}
	*ppbaco = pmbmp;
	return fTrue;
}


/***************************************************************************
	Given an FNI refering to a bitmap file, returns the interesting parts of
	the header and the pixel data and palette.  Fails if the bitmap is not
	8 bits, uncompressed.  Any or all of the output pointers may be nil.

	input:
		pfni	--	the file from which to read the data

	output:
		The following variable parameters are valid only if FReadBitmap
		returns true (*pprgb and *ppglclr will be nil if this fails):
			pprgb			-- pointer to the allocated memory for pixel data
			ppglclr			-- the palette
			pdxp			-- the width of the bitmap
			pdyp			-- the height of the bitmap
			pfUpsideDown	--	fTrue if the bitmap is upside down
		returns fTrue if it succeeds
***************************************************************************/
bool FReadBitmap(FNI *pfni, byte **pprgb, PGL *ppglclr, long *pdxp, long *pdyp,
	bool *pfUpsideDown, byte bTransparent)
{
	AssertPo(pfni, ffniFile);
	AssertNilOrVarMem(pprgb);
	AssertNilOrVarMem(ppglclr);
	AssertNilOrVarMem(pdxp);
	AssertNilOrVarMem(pdyp);
	AssertNilOrVarMem(pfUpsideDown);

#ifdef WIN
	#pragma pack(2) //the stupid bmfh is an odd number of shorts
	struct BMH
		{
		BITMAPFILEHEADER bmfh;
		BITMAPINFOHEADER bmih;
		};
	#pragma pack()

	PFIL pfil;
	RC rc;
	long fpMac, cbBitmap, cbSrc;
	BMH bmh;
	bool fRet, fRle;
	FP fpCur = 0;

	if (pvNil != pprgb)
		*pprgb = pvNil;
	if (pvNil != ppglclr)
		*ppglclr = pvNil;

	if (pvNil == (pfil = FIL::PfilOpen(pfni)))
		return fFalse;
	fpMac = pfil->FpMac();
	if (size(BMH) >= fpMac || !pfil->FReadRgbSeq(&bmh, size(BMH), &fpCur))
		goto LFail;

	fRle = (bmh.bmih.biCompression == BI_RLE8);
	cbSrc = bmh.bmih.biSizeImage;
	if (((long)bmh.bmfh.bfSize != fpMac) ||
		bmh.bmfh.bfType != 'MB' || !FIn(bmh.bmfh.bfOffBits, size(BMH), fpMac) ||
		bmh.bmfh.bfReserved1 != 0 || bmh.bmfh.bfReserved2 != 0 ||
		bmh.bmih.biSize != size(bmh.bmih) || bmh.bmih.biPlanes != 1)
		{
		Warn("bad bitmap file");
		goto LFail;
		}
	if (bmh.bmih.biBitCount != 8)
		{
		Warn("not an 8-bit bitmap");
		goto LFail;
		}
	if (bmh.bmih.biCompression != BI_RGB && !fRle ||
		cbSrc != fpMac - (long)bmh.bmfh.bfOffBits && (cbSrc != 0 || fRle))
		{
		Warn("bad compression type or bitmap file is wrong length");
		goto LFail;
		}

	rc.Set(0, 0, bmh.bmih.biWidth, LwAbs(bmh.bmih.biHeight));
	cbBitmap = CbRoundToLong(rc.xpRight) * rc.ypBottom;
	if (rc.FEmpty())
		{
		Warn("Empty bitmap rectangle");
		goto LFail;
		}
	if (!fRle)
		{
		if (cbSrc == 0)
			cbSrc = cbBitmap;
		else if (cbSrc != cbBitmap)
			{
			Warn("Bitmap data wrong size");
			goto LFail;
			}
		}

	if (pvNil != ppglclr)
		{
		// get the palette
		if (bmh.bmih.biClrUsed != 0 && bmh.bmih.biClrUsed != 256)
			{
			Warn("palette is wrong size");
			goto LFail;
			}

		if (pvNil == (*ppglclr = GL::PglNew(size(CLR), 256)))
			goto LFail;

		AssertDo((*ppglclr)->FSetIvMac(256), 0);
		fRet = pfil->FReadRgbSeq((*ppglclr)->PvLock(0),
			LwMul(size(CLR), 256), &fpCur);
		(*ppglclr)->Unlock();
		if (!fRet)
			goto LFail;
		}

	if (pvNil == pprgb)
		goto LDone;

	if (fRle)
		{
		byte *pbSrc, *pbDst, *prgbSrc, *pbLimSrc, *pbLimDst;
		byte bT;
		long xp, cbT;
		long cbRowDst = CbRoundToLong(rc.xpRight);

		// get the source
		if (!FAllocPv((void **)&prgbSrc, cbSrc, fmemNil, mprNormal))
			goto LFail;

		if (!pfil->FReadRgb(prgbSrc, cbSrc, bmh.bmfh.bfOffBits) ||
			!FAllocPv((void **)pprgb, cbBitmap, fmemNil, mprNormal))
			{
			goto LBad;
			}
		pbDst = *pprgb;
		pbSrc = prgbSrc;

		pbLimSrc = pbSrc + cbSrc;
		pbLimDst = pbDst + cbBitmap;
		xp = 0;
		for (;;)
			{
			if (2 > pbLimSrc - pbSrc)
				goto LBad;

			bT = *pbSrc++;
			if (bT != 0)
				{
				if (bT > pbLimDst - pbDst || bT > cbRowDst - xp)
					goto LBad;
				FillPb(pbDst, bT, *pbSrc++);
				pbDst += bT;
				xp += bT;
				continue;
				}

			// escaped
			bT = *pbSrc++;
			switch (bT)
				{
			case 0:
				// end of line
				if (cbRowDst > xp)
					FillPb(pbDst, cbRowDst - xp, bTransparent);
				pbDst += cbRowDst - xp;
				xp = 0;
				break;

			case 1:
				// end of bitmap
				if (pbLimDst > pbDst)
					FillPb(pbDst, pbLimDst - pbDst, bTransparent);
				goto LRleDone;

			case 2:
				// delta
				if (2 > pbLimSrc - pbSrc)
					goto LBad;
				cbT = pbSrc[0] + cbRowDst * pbSrc[1];
				if (cbT > pbLimDst - pbDst || pbSrc[0] > cbRowDst - xp)
					goto LBad;
				FillPb(pbDst, cbT, bTransparent);
				pbDst += cbT;
				xp += pbSrc[0];
				pbSrc += 2;
				break;

			default:
				// literal run
				cbT = LwRoundAway(bT, 2);
				if (cbT > pbLimSrc - pbSrc || bT > pbLimDst - pbDst ||
						bT > cbRowDst - xp)
					{
					goto LBad;
					}
				CopyPb(pbSrc, pbDst, bT);
				pbDst += bT;
				pbSrc += cbT;
				xp += bT;
				break;
				}
			}
LBad:
		// rle encoding is bad
		FreePpv((void **)&prgbSrc);
		Warn("compressed bitmap is bad");
		goto LFail;

LRleDone:
		FreePpv((void **)&prgbSrc);
		}
	else
		{
		// non-rle: get the bits
		if (!FAllocPv((void **)pprgb, cbBitmap, fmemNil, mprNormal))
			goto LFail;

		if (!pfil->FReadRgb(*pprgb, cbBitmap, bmh.bmfh.bfOffBits))
			{
			FreePpv((void **)pprgb);
LFail:
			PushErc(ercMbmpCantOpenBitmap);
			if (pvNil != pprgb)
				*pprgb = pvNil;
			if (pvNil != ppglclr)
				ReleasePpo(ppglclr);
			TrashVar(pdxp);
			TrashVar(pdyp);
			TrashVar(pfUpsideDown);
			ReleasePpo(&pfil);
			return fFalse;
			}
		}

LDone:
	if (pvNil != pdxp)
		*pdxp = bmh.bmih.biWidth;
	if (pvNil != pdyp)
		*pdyp = LwAbs(bmh.bmih.biHeight);
	if (pvNil != pfUpsideDown)
		*pfUpsideDown = bmh.bmih.biHeight < 0;
	ReleasePpo(&pfil);
	return fTrue;
#endif //WIN
#ifdef MAC
	if (pvNil != pprgb)
		*pprgb = pvNil;
	if (pvNil != ppglclr)
		*ppglclr = pvNil;

	TrashVar(pdxp);
	TrashVar(pdyp);
	TrashVar(pfUpsideDown);
	RawRtn();	// REVIEW peted: Mac FReadBitmap NYI
	return fFalse;
#endif //MAC
}


/***************************************************************************
	Writes a given bitmap to a given file.

	Arguments:
		FNI *pfni         -- the name of the file to write
		byte *prgb        -- the bits in the bitmap
		PGL pglclr        -- the palette of the bitmap
		long dxp          -- the width of the bitmap
		long dyp          -- the height of the bitmap
		bool fUpsideDown  -- indicates if the rows should be inverted

	Returns: fTrue if it could write the file
***************************************************************************/
bool FWriteBitmap(FNI *pfni, byte *prgb, PGL pglclr, long dxp, long dyp,
	bool fUpsideDown)
{
	AssertPo(pfni, ffniFile);
	AssertVarMem(prgb);
	AssertPo(pglclr, 0);
	Assert(pglclr->IvMac() == 256, "Invalid color palette");
	Assert(dxp >= 0, "Invalid width");
	Assert(dyp >= 0, "Invalid height");

#ifdef WIN
	Assert(pglclr->CbEntry() == size(RGBQUAD),
		"Palette has different format from Windows");

	#pragma pack(2) //the stupid bmfh is an odd number of shorts
	struct BMH
		{
		BITMAPFILEHEADER bmfh;
		BITMAPINFOHEADER bmih;
		};
	#pragma pack()

	bool fRet = fFalse;
	long cbSrc;
	PFIL pfil = pvNil;
	FP fpCur = 0;
	BMH bmh;

	cbSrc = CbRoundToLong(dxp) * dyp;

	/* Fill in the header */
	bmh.bmfh.bfType = 'MB';
	bmh.bmfh.bfSize = size(bmh) + LwMul(size(RGBQUAD), 256) + cbSrc;
	bmh.bmfh.bfOffBits = size(bmh) + LwMul(size(RGBQUAD), 256);
	bmh.bmfh.bfReserved1 = bmh.bmfh.bfReserved2 = 0;

	bmh.bmih.biSize = size(bmh.bmih);
	bmh.bmih.biWidth = dxp;
	bmh.bmih.biHeight = dyp;
	bmh.bmih.biPlanes = 1;
	bmh.bmih.biBitCount = 8;
	bmh.bmih.biCompression = BI_RGB;
	bmh.bmih.biSizeImage = 0;
	bmh.bmih.biXPelsPerMeter = 0;
	bmh.bmih.biYPelsPerMeter = 0;
	bmh.bmih.biClrUsed = 256;
	bmh.bmih.biClrImportant = 256;

	/* Write the header */
	if (pvNil == (pfil = FIL::PfilCreate(pfni)))
		goto LFail;
	if (!pfil->FWriteRgbSeq(&bmh, size(BMH), &fpCur))
		goto LFail;

	/* Write the palette */
	if (!pfil->FWriteRgbSeq(pglclr->PvLock(0), LwMul(size(CLR), 256), &fpCur))
		{
		pglclr->Unlock();
		goto LFail;
		}
	pglclr->Unlock();

	/* Write the bits */
	Assert((ulong)fpCur == bmh.bmfh.bfOffBits,
		"Current file pos is wrong");
	if (fUpsideDown)
		{
		byte *pbCur;
		long cbRow = CbRoundToLong(dxp);

		pbCur = prgb + dyp * cbRow;
		fRet = fTrue;
		while (pbCur > prgb && fRet)
			{
			pbCur -= cbRow;
			fRet = pfil->FWriteRgbSeq(pbCur, cbRow, &fpCur);
			}
		}
	else
		fRet = pfil->FWriteRgbSeq(prgb, cbSrc, &fpCur);

LFail:
	if (pfil != pvNil)
		{
		if (!fRet)
			pfil->SetTemp();
		ReleasePpo(&pfil);
		}
	return fRet;
#endif //WIN
#ifdef MAC
	RawRtn();	// REVIEW peted: Mac FWriteBitmap NYI
	return fFalse;
#endif //MAC
}

